<?php


namespace Hilaoyu\Utils;



class UtilAes
{
    /**
     * 返回一个当前类的实例
     * @param $key
     * @param string $cipher
     * @return static
     * @throws \Exception
     */
    static public function make($key='', $cipher = 'AES-256-CBC'){
        return new static($key, $cipher);
    }



    protected $cipher = 'AES-256-CBC';
    protected $key = '';

    /**
     * EnAes constructor.
     * @param $key
     * @param string $cipher
     * @throws \Exception
     */
    public function __construct($key = '', $cipher = 'AES-256-CBC')
    {
        $this->setKeyAndCipher($key,$cipher);
    }

    public function generateKey($cipher=''){
        if(!$cipher){
            $cipher = $this->cipher;
        }
        $cipher = strtoupper(trim($cipher));
        return UtilStr::randString($cipher === 'AES-128-CBC' ? 16 : 32);
    }

    /**
     * 设置KEY和加密方式
     * @param $key
     * @param string $cipher
     * @return $this
     * @throws \Exception
     */
    public function setKeyAndCipher($key,$cipher='AES-256-CBC'){
        $cipher = strtoupper(trim($cipher));
        if(static::supported($key,$cipher)){
            $this->key = $key;
            $this->cipher = $cipher;
        }else {
            throw new \Exception('key长度不对,AES-128-CBC 为16位，AES-256-CBC 为32位');
        }

        return $this;
    }


    /**
     * 加密
     * @param $value mixed 可以是任何可序列化的对象
     * @param bool $json_en
     * @return string
     * @throws \Exception
     */
    public function encrypt($value, $json_en = true,$base64_result=true){
        $iv = random_bytes(openssl_cipher_iv_length($this->cipher));

        $value = \openssl_encrypt(
            $json_en ? json_encode($value) : $value,
            $this->cipher, $this->key, 0, $iv
        );

        if ($value === false) {
            throw new \Exception('Could not encrypt the data.');
        }

        $enData = [
            'iv' => base64_encode($iv),
            'value' => $value,
        ];

        if(!$base64_result){
            return $enData;
        }

        $json = json_encode($enData);

        if (json_last_error() !== JSON_ERROR_NONE) {
            throw new \Exception('Could not encrypt the data.');
        }

        return base64_encode($json);
    }

    /**
     * 解密
     * @param $payload string 密文
     * @param bool $json_de
     * @return false|mixed|string
     * @throws \Exception
     */
    public function decrypt($payload, $json_de = true)
    {
        $payload = $this->getJsonPayload($payload);

        $iv = base64_decode($payload['iv']);

        // Here we will decrypt the value. If we are able to successfully decrypt it
        // we will then unserialize it and return it out to the caller. If we are
        // unable to decrypt this value we will throw out an exception message.
        $decrypted = \openssl_decrypt(
            $payload['value'], $this->cipher, $this->key, 0, $iv
        );

        if ($decrypted === false) {
            throw new \Exception('Could not decrypt the data.');
        }

        return $json_de ? json_decode($decrypted,true) : $decrypted;
    }


    /**
     * 加官字符串
     * @param $value string
     * @return string
     * @throws \Exception
     */
    public function encryptString($value)
    {
        return $this->encrypt($value, false);
    }

    /**
     * 解密字符串
     * @param $payload string 密文
     * @return false|mixed|string
     * @throws \Exception
     */
    public function decryptString($payload)
    {
        return $this->decrypt($payload, false);
    }


    protected static function supported($key, $cipher)
    {
        $length = mb_strlen($key, '8bit');

        return (in_array($cipher,['AES-256-CBC','AES-128-CBC'])) &&
            (($cipher === 'AES-128-CBC' && $length === 16) ||
                ($cipher === 'AES-256-CBC' && $length === 32));
    }

    protected function getJsonPayload($payload)
    {
        $payload = json_decode(base64_decode($payload), true);

        // If the payload is not valid JSON or does not have the proper keys set we will
        // assume it is invalid and bail out of the routine since we will not be able
        // to decrypt the given value. We'll also check the MAC for this encryption.
        if (! $this->validPayload($payload)) {
            throw new \Exception('The payload is invalid.');
        }

        return $payload;
    }

    protected function validPayload($payload)
    {
        return is_array($payload) && isset($payload['iv'], $payload['value']) &&
            strlen(base64_decode($payload['iv'], true)) === openssl_cipher_iv_length($this->cipher);
    }
}
